/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu.
 *
 * Copyright (C) 2005-2012  Dirk Beyer
 *
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 *
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */
package org.sosy_lab.ccvisu;

import static org.sosy_lab.ccvisu.CCVisuController.endl;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import javax.swing.JInternalFrame;

import org.sosy_lab.ccvisu.graph.GraphData;
import org.sosy_lab.ccvisu.measuring.calldep.DistanceVertexContainer;
import org.sosy_lab.ccvisu.measuring.calldep.colors.ColorProvider;
import org.sosy_lab.ccvisu.readers.filter.BlacklistPackageFilter;
import org.sosy_lab.ccvisu.readers.filter.Filter;
import org.sosy_lab.ccvisu.readers.filter.InternalRelations;
import org.sosy_lab.ccvisu.readers.filter.Sort;
import org.sosy_lab.ccvisu.readers.filter.Uniq;
import org.sosy_lab.ccvisu.readers.filter.WhitelistRelationFilter;
import org.sosy_lab.ccvisu.ui.interfaces.AbstractUI;
import org.sosy_lab.ccvisu.ui.interfaces.FrameDisplayMouseAdapter;
import org.sosy_lab.ccvisu.ui.interfaces.GraphLoadedListener;
import org.sosy_lab.ccvisu.ui.interfaces.WriterDataGraphicsDISPListener;
import org.sosy_lab.util.Colors;

/**
 *  Command-line options.
 */
public class Options {

  private static final String CCVISU_URL = "http://ccvisu.sosy-lab.org";
  public static final String VERSION = "CCVisu 3.5, 2012-12-24";

  // This enum construction is not yet used everywhere.
  public enum OptionsEnum {

    // General options without argument.
    help("h", "Boolean", "false", "display this help message and exit"),
    version("v", "Boolean", "false", "print version information and exit"),
    quiet("q", "Boolean", "false", "quiet mode"),
    warnings("w", "Boolean", "false", "enable warnings"),
    verbose("", "Boolean", "false", "verbose mode"),
    assertCheck("a", "Boolean", "false", "check if assertions are enabled"),
    guiMode("g", "Boolean", "false",
    "GUI mode (provides a window to set options)"),

    inputName("i", "String", "stdin", "read input data from given file <str>"),
    outputName("o", "String", "stdout", "write output data to given file <str>"),

    // Layouting options.
    dim("", "Integer", "2", "number of dimensions of the layout, up to 3"),
    iter("", "Integer", "100", "number of iterations of the minimizer;"
        + endl
        + Options.helpOptionNameIndent("")
        + "choose appropriate values by observing the convergence of energy"),
    energyDiffThreshold("", "Float", "0.0f", "layouting stop criterion: minimal threshold for energy difference between iterations"
        + endl
        + "set to 0.0f to disable this feature"),
    autoStopIterating("", "Boolean", "false", "Stop iterating if the percentage change of the energy is below a hardcoded threshold for the active repulsion strategy?"),

    initLayout("", "String", "",
            "use layout (LAY format) from file <str> as initial layout"),
    fixedInitPos("", "Boolean", "false",
            "fix positions for vertices from initial layout given by -initLayout"),

    // For CVS reader.
    timeWindow("", "Integer", "180000",
            "time window for change transaction recovery, in milli-seconds"),
    slidingTW("",
              "Boolean",
              "false",
              "use sliding time windows instead of fixed time window, i.e., the time window 'slides':"
              + endl
              + Options.helpOptionNameIndent("")
              + "a new commit node is created if the time difference between two commited files is bigger"
              + endl
              + Options.helpOptionNameIndent("")
              + "than the time window"),

    // For layout output.
    hideSource("", "Boolean", "false",
               "draw only vertices that are not source of an edge."
               + endl + Options.helpOptionNameIndent("")
               + "In co-change graphs, all change-transaction vertices"
               + endl + Options.helpOptionNameIndent("")
               + "are source vertices"),
    minVert("", "Float", "2.0f", "size of the smallest vertex disc; diameter"),

    fontSize("", "Integer", "14", "font size of vertex annotations"),
    // Draw a ring around the vertex discs (filled circles, strokes).
    blackCircle("", "Boolean", "true", ""),
    ringColor("", "String", "GRAY", "Color of the ring around the vertex discs"),
    depDegreeColor("", "Boolean", "false",
                  "Color of the vertex disc determined by dep-degree"),
    enableFeatureVisu("", "Boolean", "false", "Enables support for features"),

    // Show the Edges
    showEdges("", "Boolean", "false",
             "show the edges of the graph; available only for CVS and RFS inFomat"),
    // Options for VRML writer.
    // Scale positions in the layout.
    scalePos("", "Float", "1.0f",
             "scaling factor for the layout to adjust; VRML and SVG only"),

    // Only for DISP writer.
    /** If true, the layout is already displayed while the minimizer is still improving it,
        and a simple mouse click on the canvas updates the current layout on the screen.
        If false, the layout is displayed only after minimization is completed. */
    anim("", "Boolean", "true", ""),

    // Do not display the GUI window if the DISP writer is used
    disableDISPGuiControls("", "Boolean", "false", "don't display the GUI window for the DISP writer"),
    disableVertexDragging("", "Boolean", "false", "disable vertex dragging"),
    stretchGraphToBounds("", "Boolean", "false", "stretch visual graph representation so that it takes up whole display frame"),
    considerAuxVertices("", "Boolean", "false", "consider auxiliary vertices when calculating graphical layout representation"),

    showRefreshingLabel("", "Boolean", "true", "always inform the user that ccvisu is currently refreshing the screen"),
    enableToolTips("", "Boolean", "false", "enable tool tips for drawing canvas"),

    // For all writers.
    showAllLabels("", "Boolean", "false", "annotate each vertex with its name"),

    /** Heuristically shorten vertex labels. */
    shortNames("", "Boolean", "false", "shorten vertex labels"),

    dispFilter("", "Boolean", "false", "show extra controls for display filter"),

    // Switch on user-defined marker to emphasize certain vertices.
    initGroups("", "String", "",
        "assign vertices to groups (and colors) according to file <str>"
        + endl + Options.helpOptionNameIndent("")
        + "(cf. file marker_script.example.txt as example)"),
        computeCut("", "Boolean", "false", "interpret group assignment by -initGroups as partition and compute the cut"),

    writeClusteringMeasures("", "String", "",
        "Calculate the measures of the given clustering (group file) and write it to the specified file."),

    clusters("", "Integer", "0", "(max) number of clusters to extract"),
    clusterMergeDistancePercent("", "Float", "0.03f", "Merge partitions within distance (percent of layout diagonal; e.g. 0.05)."),
    clusterMinDistancePercent("", "Float", "0.4f", "Do not merge a pair of clusters if the distance "
                            + endl + Options.helpOptionNameIndent("") + "of their barycenters is larger than this value (percent of layout diagonal)."),

    // Dependency visualization
    cdep("", "Boolean", "false", "Calculate and visualize dependencies."),
    dependencyColoring("", "String", ColorProvider.getInstance().getDefaultColorOption(),
        "Set the coloring scheme used for dependency visualization."),
    dependencyShowLabels("", "Integer", "3",
        "Set the number of labels to automatically show when calculating dependencies"),
    dependencyAlgorithm("", "String", DistanceVertexContainer.ALGORITHM_NAME,
        "Set the algorithm for dependency calculations"),

    // Extract information from source code
    sourceDirectory("srcDir", "String", "", "Path to the source code. " +
        "This is required when extracting from sourcecode."),
    sourceLibraries("srcLibs", "String", "", "Path to the libraries"),
    sourceBlacklist("srcBlacklst", "String", "", "List of all packages to be " +
        "ignored when creating the relations. Strings must be separated by ';'"),
    sort("sort", "Boolean", "false", "Sort output relations."),
    uniq("uniq", "Boolean", "false", "Output every tuple at most once. This option sorts implicitly."),
    internal("internal", "Boolean", "false", "Use only internal relations."),

    openURL(
        "",
        "Boolean",
        "false",
        "the vertex names can be considered as URL and opened in a web browser."
        + endl
        + Options.helpOptionNameIndent("")
        + "This option used with DISP output requires to hold CTRL KEY while clicking"),

    browser("", "String", "",
        "browser <str> will be invoked; if empty, CCVisu will try to guess");

    private final String altOptName;
    private final String type;
    private final String defaultValue;
    private final String description;

    OptionsEnum(String altOptName, String type, String defaultValue,
        String description) {
      this.altOptName = altOptName;
      this.type = type;
      this.defaultValue = defaultValue;
      this.description = description;
    }

    String getAltOptName() {
      return altOptName;
    }

    String getType() {
      return type;
    }

    public String getDefault() {
      return defaultValue;
    }

    public String getDescription() {
      return description;
    }
  }

  public class Option {
    private final String optName;
    private final String altOptName;
    private final String type;
    private final String defaultValue;
    private final String description;

    private Boolean      valueBoolean;
    private Integer      valueInteger;
    private Float        valueFloat;
    private String       valueString;

    Option(String optName, String altOptName, String type, String defaultValue,
        String description) {

      this.optName = optName;
      this.altOptName = altOptName;
      this.type = type;
      this.defaultValue = defaultValue;
      this.description = description;

      if (type.equals("Boolean")) {
        valueBoolean = new Boolean(defaultValue);
      }
      if (type.equals("Integer")) {
        valueInteger = new Integer(defaultValue);
      }
      if (type.equals("Float")) {
        valueFloat = new Float(defaultValue);
      }
      if (type.equals("String")) {
        valueString = new String(defaultValue);
      }
    }

    public String getOptName() {
      return optName;
    }

    public String getAltOptName() {
      return altOptName;
    }

    public String getType() {
      return type;
    }

    public String getDefault() {
      return defaultValue;
    }

    public String getDescription() {
      return description;
    }

    public boolean getBool() {
      assert (valueBoolean != null);
      return valueBoolean;
    }

    public void set(boolean value) {
      assert (valueBoolean != null);
      valueBoolean = new Boolean(value);
    }

    public int getInt() {
      assert (valueInteger != null);
      return valueInteger;
    }

    public void set(int value) {
      assert (valueInteger != null);
      valueInteger = new Integer(value);
    }

    public float getFloat() {
      assert (valueFloat != null);
      return valueFloat;
    }

    public void set(float pValue) {
      assert (valueFloat != null);
      valueFloat = new Float(pValue);
    }

    public String getString() {
      assert (valueString != null);
      return valueString;
    }

    public void set(String value) {
      assert (valueString != null);
      valueString = new String(value);
    }

    public String getValue() {
      String value = "";
      if (type.equals("Boolean")) {
        value = valueBoolean.toString();
      }
      if (type.equals("Integer")) {
        value = valueInteger.toString();
      }
      if (type.equals("Float")) {
        value = valueFloat.toString();
      }
      if (type.equals("String")) {
        value = "'" + valueString.toString() + "'";
      }
      return value;
    }

    @Override
    public String toString() {
      return optName;
    }
  }

  private Map<OptionsEnum, Option> settings = new HashMap<OptionsEnum, Option>();

  private Option getOption(String optionName) {
    for (Option o : settings.values()) {
      if (optionName.equalsIgnoreCase("-" + o.toString())) { return o; }
      if (optionName.equalsIgnoreCase("-" + o.altOptName)) { return o; }
      if (optionName.equalsIgnoreCase("--" + o.toString())) { return o; }
      if (optionName.equalsIgnoreCase("--" + o.altOptName)) { return o; }
    }
    return null;
  }

  public Option getOption(OptionsEnum option) {
    return settings.get(option);
  }

  /** Input formats. */
  public enum InFormat {
    RSF("rsf", "Relational Standard Files",
        "Graph (relation) in Relational Standard Format (RSF).", true),

    LAY("lay", "Layout Files", "Graph layout in textual format.", true),

    CVS("log", "CVS Log Files", "CVS log format (produce with 'cvs log -Nb').", true),

    SVN("xml", "SVN Log Files in XML",
        "SVN log format (produce with 'svn log -v --xml').", true),

    SRC("src", "Java Source Code",
        "Use the java source of a software system as input.", false),

    DOX("xml", "Doxygen Output as XML Files",
        "Doxygen XML dump format (produce with 'doxygen').", true),

    ODS("ods", "Open Document Spreadsheet Files", "ODS Spreadsheet.", true),

    AUX("aux", "Auxiliary Graph Input",
        "Graph is passed as data structure from a third-party client.", false);

    private final String fileExtension;
    private final String shortDescription;
    private final String description;
    private final boolean defaultGuiLoad;

    /**
     * Constructor of a file format.
     *
     * @param fileExtension The ending of the file.
     * @param shortDescription Short description of the input file type.
     * @param description Long description of the input file type.
     * @param defaultGuiLoad <code>true</code> if the file format is to be shown as a
     * default load option in the GUI loader, <code>false</code> otherwise.
     */
    InFormat(String fileExtension, String shortDescription, String description,
        boolean defaultGuiLoad) {

      this.fileExtension = fileExtension;
      this.shortDescription = shortDescription;
      this.description = description;
      this.defaultGuiLoad = defaultGuiLoad;
    }

    /**
     * @return the fileExtension
     */
    public String getFileExtension() {
      return '.' + fileExtension;
    }

    /**
     * @return A short description of the input format.
     */
    public String getShortDescription() {
      return shortDescription;
    }

    public String getDescription() {
      return description;
    }

    public boolean isDefaultGuiLoad() {
      return this.defaultGuiLoad;
    }
  }

  /** Output formats. */
  public enum OutFormat {
    RSF("rsf", "Relational Standard Files",
        "Graph (relation) in Relational Standard Format (RSF)."),

    CRSF("rsf", "Relational Standard Files containing Clusters",
        "Graph clustering in Relational Standard Format (RSF)."),

    LAY("lay", "Layout Files", "Graph layout in textual format."),

    SVG("svg", "SVG Files", "Graph layout in SVG format."),

    VRML("wrl", "VRML Files", "Graph layout in VRML format."),

    GML("gml", "GraphML Files", "Graph in GraphML format."),

    GXL("gxl", "GXL Files", "Graph in GXL format."),

    DISP("disp", "Screen Output", "Display graph layout on screen."),

    STATS("stats", "Statistics Files", "Statistics in textual format."),

    CALLDEP("rsf", "Calldep output", "Output of the calldep algorithm."),

    DIST("dst", "Node-pair distances",
        "Euclidian-distances between all node-pairs of the graph."),

    NULL("null", "Null output", "Null output");

    private final String fileExtension;
    private final String shortDescription;
    private final String description;

    OutFormat(String fileExtension, String shortDescription, String description) {
      this.fileExtension = fileExtension;
      this.shortDescription = shortDescription;
      this.description = description;
    }

    /**
     * @return the fileExtension
     */
    public String getFileExtension() {
      return '.' + fileExtension;
    }

    /**
     * @return the formatName
     */
    public String getShortDescription() {
      return shortDescription;
    }

    public String getDescription() {
      return description;
    }
  }

  /** Verbosity levels. */
  public enum Verbosity {
    /** Don't say much, only report errors. */
    QUIET(0),
    /** Print warnings as well. */
    WARNING(1),
    /** Print light warnings as well. */
    WARNINGLIGHT(2),
    /** Print statistics and other information. */
    VERBOSE(3),
    /** Print debugging information as well. */
    DEBUG(4);

    private int level;

    Verbosity(int level) {
      this.level = level;
    }

    public boolean isAtLeast(Verbosity verbosity) {
      return this.level >= verbosity.level;
    }
  }

  // Input format
  public InFormat                        inFormat                       = InFormat.RSF;

  // Output format.
  OutFormat                              outFormat                      = OutFormat.DISP;

  /** Filters that are applied to the given Relation. */
  Collection<Filter>                     filters                        = new LinkedList<Filter>();

  public GraphData                       initialLayout                  = null;

  public FrameDisplayMouseAdapter        frameDisplayMouseAdapter       = null;

  public WriterDataGraphicsDISPListener  writerDataGraphicsDISPListener = null;

  public AbstractUI                      gui                            = null;

  // For energy model.
  /* Exponent of the Euclidian distance in the attraction
   * term of the energy (default: 1). */
  public float                           attrExponent                   = 1.0f;
  // Exponent of the Euclidian distance in the repulsion term of the energy (default: 0).
  public float                           repuExponent                   = 0.0f;
  /* Use vertex repulsion instead of edge repulsion,
   *   true for vertex repulsion, false for edge repulsion
   *   (default: edge repulsion). */
  public boolean                         vertRepu                       = false;
  /* Use unweighted model by ignoring the edge weights,
   *   true for unweighted, false for weighted (default: weighted). */
  public boolean                         noWeight                       = false;
  /* Gravitation factor for the Barnes-Hut-procedure,
   *   attraction to the barycenter (default: 0.001). */
  public float                           gravitation                    = 0.001f;

  /** Background color for layout output. */
  public Colors                          backColor                      = Colors.WHITE;

  public JInternalFrame                  frame                          = null;

  /** In/Out parameter representing the graph. */
  public final GraphData graph;

  public InformationCollector            infoCollector;

  public Verbosity                       verbosity                      = Verbosity.WARNING;

  public Collection<GraphLoadedListener> graphLoadedListeners           = new LinkedList<GraphLoadedListener>();

  public Options(GraphData graph) {
    this.graph = graph;

    for (OptionsEnum o : OptionsEnum.values()) {
      settings.put(o, new Option(o.toString(), o.getAltOptName(),
                   o.getType(), o.getDefault(), o.getDescription()));
    }
  }

  /**
   * Parses command-line options.
   * @param args    Array of command-line options.
   */
  public void parseCmdLine(String[] args) {
    List<String> argsList = Arrays.asList(args);

    if (argsList.size() == 0) {
      settings.get(OptionsEnum.guiMode).set(true);
    }

    Collection<String> whitelist = new LinkedList<String>();

    for (ListIterator<String> it = argsList.listIterator(); it.hasNext();) {
      String arg = Options.getNext(it);

      // General options with argument.
      // Input format.
      if (arg.equalsIgnoreCase("-inFormat")) {
        arg = Options.getNext(it);
        inFormat = Options.getInFormat(arg);
      }
      // Output format.
      else if (arg.equalsIgnoreCase("-outFormat")) {
        arg = Options.getNext(it);
        outFormat = Options.getOutFormat(arg);
      }

      // Options for DOX reader.
      // Relations that should be used for computing layout.
      else if (arg.equalsIgnoreCase("-relSelect")) {
        arg = Options.getNext(it);
        whitelist.add(arg);
      }

      // Energy model.
      // Attraction exponent.
      else if (arg.equalsIgnoreCase("-attrExp")) {
        arg = Options.getNext(it);
        attrExponent = Float.parseFloat(arg);
      }
      // Repulsion exponent.
      else if (arg.equalsIgnoreCase("-repuExp")) {
        arg = Options.getNext(it);
        repuExponent = Float.parseFloat(arg);
      }
      // Node repulsion.
      else if (arg.equalsIgnoreCase("-vertRepu")) {
        vertRepu = true;
      }
      // No weights.
      else if (arg.equalsIgnoreCase("-noWeight")) {
        noWeight = true;
      }
      // Gravitation factor.
      else if (arg.equalsIgnoreCase("-grav")) {
        arg = Options.getNext(it);
        gravitation = Float.parseFloat(arg);
      }

      // TODO: Remove/convert to new the following:

      // Options for output writers.
      // Avoid black circles around the filled circles for vertices (strokes).
      else if (arg.equalsIgnoreCase("-noBlackCircle")) {
        settings.get(OptionsEnum.blackCircle).set(false);
      }
      // Background color.
      else if (arg.equalsIgnoreCase("-backcolor")) {
        arg = Options.getNext(it);
        backColor = Colors.valueOfUpper(arg);
      }

      // Only for display writer.
      // Animation of layout during minimization, if outFormat is DISP.
      else if (arg.equalsIgnoreCase("-noAnim")) {
        settings.get(OptionsEnum.anim).set(false);
      }

      // 'Automatic' option.
      else if (getOption(arg) != null) {
        Option option = getOption(arg);
        if (option.getType().equals("Boolean")) {
          option.set(true);
        } else if (option.getType().equals("Integer")) {
          arg = Options.getNext(it);
          option.set(Integer.parseInt(arg));
        } else if (option.getType().equals("Float")) {
          arg = Options.getNext(it);
          option.set(Float.parseFloat(arg));
        } else if (option.getType().equals("String")) {
          arg = Options.getNext(it);
          option.set(arg);
        }
      }

      // Unknown option.
      else {
        System.err.println("Usage error: Option '" + arg + "' unknown.");
        System.exit(1);
      }
    } // for parsing command-line

    // Set verbosity...
    if (getOption(OptionsEnum.quiet).getBool()) {
      verbosity = Verbosity.QUIET;
    } else if (getOption(OptionsEnum.warnings).getBool()) {
      verbosity = Verbosity.WARNING;
    } else if (getOption(OptionsEnum.verbose).getBool()) {
      verbosity = Verbosity.VERBOSE;
    }

    setFilters(whitelist);
  }

  private void setFilters(Collection<String> whitelist) {
    // the order of the following is relevant
    // as tuples will get rearranged, changed and rewritten

    if (getOption(OptionsEnum.internal).getBool()) {
      filters.add(new InternalRelations(verbosity));
    }

    if (whitelist.size() > 0) {
      filters.add(new WhitelistRelationFilter(whitelist));
    }

    if (getOption(OptionsEnum.sort).getBool()
        && !getOption(OptionsEnum.uniq).getBool()) { // uniq sorts implicitly

      filters.add(new Sort());
    }

    if (getOption(OptionsEnum.uniq).getBool()) {
      filters.add(new Uniq());
    }

    if (!getOption(OptionsEnum.sourceBlacklist).getString().isEmpty()) {
      Collection<String> blacklistedPackages =
          Arrays.asList(getOption(OptionsEnum.sourceBlacklist).getString().split(";"));
      filters.add(new BlacklistPackageFilter(blacklistedPackages));
    }
  }

  /**
   * Checks whether the current command-line argument a parameter.
   * If there is no follower argument, it exits the program.
   * @param it    String list iterator that points to the current argument.
   */
  private static String getNext(ListIterator<String> it) {
    if (!it.hasNext()) {
      System.err.println("Usage error: Option '" + it.previous()
          + "' requires an argument (file).");
      System.exit(1);
    }
    return it.next();
  }

  /**
   * Returns a String with the version information.
   */
  public String versionMessage() {
    StringBuilder versionText = new StringBuilder();

    versionText.append(Options.VERSION + "." + endl);
    versionText.append("Copyright (C) 2005-2010  Dirk Beyer (Uni Passau, Germany)." + endl);
    versionText.append("CCVisu is free software, released under the GNU LGPL. " + endl);

    return versionText.toString();
  }

  /**
   * Returns a brief tool description.
   */
  public static String toolDescription() {
    return "CCVisu, a tool for visual graph clustering and "
    + "general force-directed graph layout.";
  }

  /**
   * Returns data and time in ISO format.
   */
  public static String currentDateTime() {
    return (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(new Date());
  }

  /**
   * Returns usage information.
   */
  public String helpMessage() {
    StringBuilder msg = new StringBuilder();

    msg.append(endl);
    msg.append("This is " + Options.toolDescription() + endl);
    msg.append(CCVISU_URL + endl);
    msg.append(endl);
    msg.append("Usage: java ccvisu.CCVisu [OPTION]..." + endl);
    msg.append("Compute a layout for a given (co-change) graph (or convert)." + endl);
    msg.append(endl);
    msg.append("Options: " + endl);

    msg.append("General options: " + endl);
    msg.append(Options.helpForOption(OptionsEnum.help));
    msg.append(Options.helpForOption(OptionsEnum.version));
    msg.append(Options.helpForOption(OptionsEnum.quiet));
    msg.append(Options.helpForOption(OptionsEnum.warnings));
    msg.append(Options.helpForOption(OptionsEnum.verbose));
    msg.append(Options.helpForOption(OptionsEnum.assertCheck));
    msg.append(Options.helpForOption(OptionsEnum.guiMode));
    msg.append(Options.helpForOption(OptionsEnum.inputName));
    msg.append(Options.helpForOption(OptionsEnum.outputName));
    msg.append("   -inFormat FORMAT  read input data in format FORMAT (default: RSF, see below)." + endl);
    msg.append("   -outFormat FORMAT write output data in format FORMAT (default: DISP, see below)." + endl);
    msg.append(endl);

    msg.append("Layouting options:" + endl);
    msg.append(Options.helpForOption(OptionsEnum.dim));
    msg.append(Options.helpForOption(OptionsEnum.iter));
    msg.append(Options.helpForOption(OptionsEnum.initLayout));
    msg.append(Options.helpForOption(OptionsEnum.fixedInitPos));
    msg.append(endl);
    msg.append("Energy model options:" + endl);
    msg.append("   -attrExp <int>    exponent for the distance in the attraction term" + endl);
    msg.append("                     (default: 1)." + endl);
    msg.append("   -repuExp <int>    specifies that <int> is applied as exponent to the distance in the repulsion term" + endl);
    msg.append("                     (if <int> != 0) or that log is applied to the distance (if <int> = 0) (default: 0)." + endl);
    msg.append("   -vertRepu         use vertex repulsion instead of edge repulsion" + endl);
    msg.append("                     (default: edge repulsion). " + endl);
    msg.append("   -noWeight         use unweighted model (default: weighted)." + endl);
    msg.append("   -grav <float>     gravitation factor for the Barnes-Hut-procedure" + endl);
    msg.append("                     (default: 0.001)." + endl);
    msg.append(endl);

    msg.append("Dependency visualization options: " + endl);
    msg.append(Options.helpForOption(OptionsEnum.cdep));
    msg.append(Options.helpForOption(OptionsEnum.enableToolTips));
    msg.append(endl);

    msg.append("DOX reader option:" + endl);
    msg.append("   -relSelect <rel>  selects a relation for visualization" + endl);
    msg.append("                     (default: REFFILE). " + endl);
    msg.append(endl);

    msg.append("CVS reader options:" + endl);
    msg.append(Options.helpForOption(OptionsEnum.timeWindow));
    msg.append(Options.helpForOption(OptionsEnum.slidingTW));
    msg.append(endl);

    msg.append("SRC reader options: " + endl);
    msg.append(Options.helpForOption(OptionsEnum.sourceDirectory));
    msg.append(Options.helpForOption(OptionsEnum.sourceLibraries));
    msg.append(Options.helpForOption(OptionsEnum.sourceBlacklist));
    msg.append(endl);

    msg.append("Layout writer options:" + endl);
    msg.append(Options.helpForOption(OptionsEnum.hideSource));
    msg.append(Options.helpForOption(OptionsEnum.minVert));
    msg.append(Options.helpForOption(OptionsEnum.fontSize));
    msg.append("   -backColor COLOR  background color (default: WHITE)." + endl);
    msg.append("                     Colors: BLACK, GRAY, LIGHTGRAY, WHITE." + endl);
    msg.append("   -noBlackCircle    no black circle around each vertex (default: with). " + endl);
    msg.append(Options.helpForOption(OptionsEnum.ringColor));
    msg.append(Options.helpForOption(OptionsEnum.depDegreeColor));
    msg.append(Options.helpForOption(OptionsEnum.enableFeatureVisu));
    msg.append(Options.helpForOption(OptionsEnum.showEdges));
    msg.append(Options.helpForOption(OptionsEnum.scalePos));
    msg.append("   -noAnim           layout not shown while minimizer is still improving it " + endl);
    msg.append("                     (default: show). " + endl);
    msg.append(Options.helpForOption(OptionsEnum.showAllLabels));
    msg.append(Options.helpForOption(OptionsEnum.shortNames));
    msg.append(Options.helpForOption(OptionsEnum.dispFilter));
    msg.append(Options.helpForOption(OptionsEnum.initGroups));
    msg.append(Options.helpForOption(OptionsEnum.openURL));
    msg.append(endl);

    msg.append("Clustering (partitioning) options:" + endl);
    msg.append(Options.helpForOption(OptionsEnum.clusterMinDistancePercent));
    msg.append(Options.helpForOption(OptionsEnum.clusters));
    msg.append(endl);

    msg.append("DISP specific option" + endl);
    msg.append(Options.helpForOption(OptionsEnum.browser));
    msg.append(endl);

    msg.append("Input Formats:" + endl);
    for (InFormat f : InFormat.values()) {
      msg.append(Options.helpOptionNameIndent("   " + f.toString() + " ") + f.getDescription() + endl);
    }

    msg.append("Output Formats:" + endl);
    for (OutFormat f : OutFormat.values()) {
      msg.append(Options.helpOptionNameIndent("   " + f.toString() + " ") + f.getDescription() + endl);
    }

    msg.append(endl);
    msg.append(CCVISU_URL + endl);
    msg.append(endl);
    msg.append(endl);
    msg.append("Report bugs to Dirk Beyer <firstname.lastname@uni-passau.de>." + endl);

    return msg.toString();
  }

  private static String helpForOption(OptionsEnum option) {
    String argText = "";
    if (option.getType().equals("Integer")) {
      argText = " <int>";
    }
    if (option.getType().equals("Float")) {
      argText = " <float>";
    }
    if (option.getType().equals("String")) {
      argText = " <str>";
    }
    String lOption = "-" + option.toString();
    if (!option.altOptName.equals("") && option.type.equals("Boolean")) {
      lOption = "-" + option.altOptName + "  --" + option.toString();
    }
    return Options.helpOptionNameIndent("   " + lOption + argText + " ")
    + option.getDescription() + " (default value: "
    + option.getDefault() + "). " + endl;
  }

  private static String helpOptionNameIndent(String string) {
    String indent = "                     ";
    if (string.length() >= indent.length()) {
      return string;
    }
    return string + indent.substring(string.length(), indent.length());
  }

  /**
   * Transforms the format given as a string into the appropriate enum.
   * @param formatStr  File format string to be converted to enum.
   * @return           File format identifier.
   */
  private static InFormat getInFormat(String formatStr) {
    InFormat result = InFormat.RSF;
    try {
      result = InFormat.valueOf(formatStr.toUpperCase());
    } catch (Exception e) {
      Options.handleFormatException(formatStr);
    }
    return result;
  }

  /**
   * Get input format of file by extension.
   * @param fileName
   * @return Format.
   */
  public static InFormat getInFormatOfFile(String fileName) {
    for (InFormat f: InFormat.values()) {
      if (fileName.endsWith(f.fileExtension)) {
        return f;
      }
    }
    return InFormat.RSF;
  }

  /**
   * Get output format by file extension.
   *
   * @param fileExtension
   * @return Format.
   */
  public static OutFormat getOutFormatByExtension(String fileExtension) {
    for (OutFormat f : OutFormat.values()) {
      if ((fileExtension).equalsIgnoreCase(f.getFileExtension())) {
        return f;
      }
    }
    return OutFormat.NULL;
  }

  /**
   * Transforms the format given as a string into the appropriate enum.
   * @param formatStr  File format string to be converted to enum.
   * @return           File format identifier.
   */
  private static OutFormat getOutFormat(String formatStr) {
    OutFormat result = OutFormat.RSF;
    try {
      result = OutFormat.valueOf(formatStr.toUpperCase());
    } catch (Exception e) {
      Options.handleFormatException(formatStr);
    }
    return result;
  }

  /**
   * Handles exceptions of format conversions.
   * @param formatStr    input/output format as string
   */
  private static void handleFormatException(String formatStr) {
    System.err.println("Usage error: '" + formatStr
        + "' is not a valid input/output format.");
    System.exit(1);
  }

}
